#include "stdafx.h"
#include "SimManager.h"
#include "CommonWM.h"

#ifdef _DEBUG
#include "crtdbg.h"
#endif

#define MAX_THREAD_WAIT_TIME     20000
#define EVT_SIMMANAGERACTION   TEXT("SimManagerAction")

HWND SimManager::hWnd = NULL;
bool SimManager::isRunning = false;
bool SimManager::isTerminating = false;
bool SimManager::isBusy = false;
HANDLE SimManager::hThread = NULL;
DWORD  SimManager::dwThread = 0;
bool SimManager::delayLoad = false;
bool SimManager::sendNotify = false;
// sync
HANDLE           SimManager::evtAction = NULL;
CRITICAL_SECTION SimManager::csSync;
// sim
HSIM SimManager::hSim = NULL;
SimContactList SimManager::simList;

bool SimManager::Initialize(HWND hWindow, bool sendNotifications, bool delayedLoading)
{
  if (isRunning || hWindow == NULL)
    return false;

  hWnd = hWindow;
  sendNotify = sendNotifications;
  delayLoad = delayedLoading;

  evtAction = CreateEvent(NULL, FALSE, FALSE, EVT_SIMMANAGERACTION);
  if (evtAction == NULL)
    return false;

  InitializeCriticalSection(&csSync);

  hThread = CreateThread(NULL, 0, SimManager::MainThread, NULL, 0, &dwThread);
  if (hThread == NULL)
  {
    return false;
  }

  if (!delayLoad)
  {
    SetEvent(evtAction);
  }

  isRunning = true;
  return true;
}

void SimManager::Terminate()
{
  if (!isRunning)
    return;

  SyncTerminate(true);

  WaitForSingleObject(hThread, MAX_THREAD_WAIT_TIME);

  EnterCriticalSection(&csSync);
  simList.Clear();
  LeaveCriticalSection(&csSync);

  DeleteCriticalSection(&csSync);

  isRunning = false;
  isTerminating = false;
  isBusy = false;
  hThread = NULL;
  dwThread = 0;
  hWnd = NULL;
}

long SimManager::GetContactsCount()
{
  long count = 0;
  EnterCriticalSection(&csSync);
  count = simList.GetCount();
  LeaveCriticalSection(&csSync);
  return count;
}

bool SimManager::GetContact(long index, TCHAR* szName, size_t nName, TCHAR* szNumber, size_t nNumber)
{
  if (szName == NULL || szNumber == NULL)
    return false;

  bool res;
  EnterCriticalSection(&csSync);
  const SimContact* contact = simList.Get(index);
  if (contact && contact->isActive)
  {
    StringCchCopy(szName, nName, contact->szName);
    StringCchCopy(szNumber, nNumber, contact->szNumber);
    res = true;
  }
  else
  {
    res = false;
  }
  LeaveCriticalSection(&csSync);

  return res;
}

long SimManager::GetActiveContactsCount()
{
  long count = 0;
  EnterCriticalSection(&csSync);
  count = simList.GetCount();
  LeaveCriticalSection(&csSync);

  long activeCount = 0;
  for (long i = 0; i < count; i++)
  {
    bool isActive;
    EnterCriticalSection(&csSync);
    const SimContact* contact = simList.Get(i);
    isActive = contact ? contact->isActive : false;
    LeaveCriticalSection(&csSync);
    if (isActive)
      activeCount++;
  }

  return activeCount;
}

bool SimManager::GetNextActiveContact(long& index, TCHAR* szName, size_t nName, TCHAR* szNumber, size_t nNumber)
{
  if (szName == NULL || szNumber == NULL)
    return false;

  long count = 0;
  EnterCriticalSection(&csSync);
  count = simList.GetCount();
  LeaveCriticalSection(&csSync);
  if (index < 0 || index >= count)
    return false;

  bool found = false;
  for (; index < count; index++)
  {
    EnterCriticalSection(&csSync);
    const SimContact* contact = simList.Get(index);
    if (contact && contact->isActive)
    {
      StringCchCopy(szName, nName, contact->szName);
      StringCchCopy(szNumber, nNumber, contact->szNumber);
      index++;
      found = true;
    }
    LeaveCriticalSection(&csSync);
    if (found)
      break;
  }

  return found;
}

void SimManager::BeginLoading()
{
  SetEvent(evtAction);
  delayLoad = false;
}

void SimManager::SyncTerminate(bool terminate)
{
  EnterCriticalSection(&csSync);
  isTerminating = terminate;
  LeaveCriticalSection(&csSync);
  SetEvent(evtAction);
}

bool SimManager::SyncIsTerminating()
{
  bool terminating;
  EnterCriticalSection(&csSync);
  terminating = isTerminating;
  LeaveCriticalSection(&csSync);
  return terminating;
}

void SimManager::SyncSetBusy(bool busy)
{
  ::EnterCriticalSection(&csSync);
  isBusy = busy;
  ::LeaveCriticalSection(&csSync);
  ::PostMessage(hWnd, NM_THREAD_BUSY, 0, busy ? TRUE : FALSE);
}

bool SimManager::SyncAddItem(DWORD simIndex, const TCHAR* szName, const TCHAR* szNumber)
{
  bool result = false;
  EnterCriticalSection(&csSync);
  result = simList.Add(simIndex, szName, szNumber);
  LeaveCriticalSection(&csSync);
  return result;
}

void SimManager::SyncDeleteItem(DWORD simIndex)
{
  EnterCriticalSection(&csSync);
  simList.Delete(simIndex);
  LeaveCriticalSection(&csSync);
}

void SimManager::SyncSortList()
{
  EnterCriticalSection(&csSync);
  simList.Sort();
  LeaveCriticalSection(&csSync);
}

void SimManager::SimCallback(DWORD dwNotifyCode, const void* pData, DWORD dwDataSize, DWORD dwParam)
{
  if (hSim == NULL)
    return;

  SIMPBECHANGE* pChange;

  switch(dwNotifyCode)
  {
  case SIM_NOTIFY_PBE_STORED:
    pChange = (SIMPBECHANGE*)pData;
    if (pChange && pChange->dwStorage == SIM_PBSTORAGE_SIM)
    {
      SyncSetBusy(true);
      SIMPHONEBOOKENTRY pbEntry;
      pbEntry.cbSize = sizeof(SIMPHONEBOOKENTRY);
      HRESULT hr = SimReadPhonebookEntry(hSim, SIM_PBSTORAGE_SIM, pChange->dwEntry, &pbEntry);
      if (SUCCEEDED(hr))
      {
        if (SyncAddItem(pChange->dwEntry, pbEntry.lpszText, pbEntry.lpszAddress))
        {
          SyncSortList();
          PostMessage(hWnd, NM_SIM_MANAGER_CONTACT_CREATED, (WPARAM)pChange->dwEntry, 0);
        }
      }
      SyncSetBusy(false);
    }
    break;

  case SIM_NOTIFY_PBE_DELETED:
    pChange = (SIMPBECHANGE*)pData;
    if (pChange && pChange->dwStorage == SIM_PBSTORAGE_SIM)
    {
      SyncDeleteItem(pChange->dwEntry);
      SyncSortList();
      PostMessage(hWnd, NM_SIM_MANAGER_CONTACT_DELETED, (WPARAM)pChange->dwEntry, 0);
    }
    break;
  }
}

DWORD SimManager::MainThread(LPVOID lpInstance)
{
  HANDLE eventAction = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVT_SIMMANAGERACTION);
  if (eventAction == NULL)
  {
    return 1;
  }

  WaitForSingleObject(eventAction, INFINITE);

  if (SyncIsTerminating())
  {
    return 0;
  }

  DWORD indexMin = 0;
  DWORD indexMax = 0;
  HRESULT hr;

  hr = SimInitialize(sendNotify ? SIM_INIT_SIMCARD_NOTIFICATIONS : 0,
                     SimCallback, NULL, &hSim);
  if (FAILED(hr))
  {
    return 1;
  }

  SIMCAPS caps;
  caps.cbSize = sizeof(SIMCAPS);
  if (FAILED(SimGetDevCaps(hSim, SIM_CAPSTYPE_PBINDEXRANGE, &caps)) ||
      caps.dwMinPBIndex == caps.dwMaxPBIndex)
  {
    SimDeinitialize(hSim);
    return 1;
  }
  indexMin = caps.dwMinPBIndex;
  indexMax = caps.dwMaxPBIndex;

  if (!simList.Create(indexMax - indexMin + 1))
  {
    SimDeinitialize(hSim);
    return 1;
  }

  DWORD dwUsed = 0;
  DWORD dwTotal = 0;
  SimGetPhonebookStatus(hSim, SIM_PBSTORAGE_SIM, &dwUsed, &dwTotal);

  SIMPHONEBOOKENTRY pbEntry;
  pbEntry.cbSize = sizeof(SIMPHONEBOOKENTRY);

  SyncSetBusy(true);
  dwTotal = 0;
  for (DWORD i = indexMin; i <= indexMax; i++)
  {
    if (SimManager::SyncIsTerminating())
      break;

    hr = SimReadPhonebookEntry(hSim, SIM_PBSTORAGE_SIM, i, &pbEntry);
    if (SUCCEEDED(hr))
    {
      SyncAddItem(i, pbEntry.lpszText, pbEntry.lpszAddress);
      dwTotal++;
      if (dwTotal >= dwUsed)
        break;
    }
  }
  SyncSetBusy(false);

  if (SyncIsTerminating())
  {
    return 0;
  }

  if (dwTotal > 0)
  {
    SyncSortList();
    PostMessage(hWnd, NM_SIM_MANAGER_CONTACTS_LOADED, 0, 0);
  }

  // Wait for terminating event
  WaitForSingleObject(eventAction, INFINITE);

  simList.Clear();
  SimDeinitialize(hSim);
  hSim = NULL;
  return 0;
}

// LIST

bool SimContactList::Create(long numItems)
{
  Clear();
  
  if (numItems > 0)
  {
    items = new SimContact[numItems];
    if (!items)
      return false;
    count = numItems;
  }
  return true;
}

void SimContactList::Clear()
{
  if (items)
    delete[] items;
  items = NULL;
  count = 0;
}

bool SimContactList::Add(DWORD simIndex, const TCHAR* szName, const TCHAR* szNumber)
{
  long index = Find(simIndex);
  if (index >= 0)
  { // update
    if (szName)
      StringCchCopy(items[index].szName, MAX_LENGTH_PHONEBOOKENTRYTEXT, szName);
    else
      items[index].szName[0] = 0;
    if (szNumber)
      StringCchCopy(items[index].szNumber, MAX_LENGTH_ADDRESS, szNumber);
    else
      items[index].szNumber[0] = 0;
    items[index].isActive = true;
    return true;
  }
  else
  { // add
    for (long i = 0; i < count; i++)
    {
      if (!items[i].isActive)
      {
        items[i].simIndex = simIndex;
        if (szName)
          StringCchCopy(items[i].szName, MAX_LENGTH_PHONEBOOKENTRYTEXT, szName);
        if (szNumber)
          StringCchCopy(items[i].szNumber, MAX_LENGTH_ADDRESS, szNumber);
        items[i].isActive = true;
        return true;
      }
    }
  }
  return false;
}

void SimContactList::Delete(DWORD simIndex)
{
  long index = Find(simIndex);
  if (index >= 0)
  {
    items[index].isActive = false;
  }
}

long SimContactList::Find(DWORD simIndex)
{
  for (long i = 0; i < count; i++)
  {
    if (items[i].simIndex == simIndex)
      return i;
  }
  return -1;
}

void SimContactList::Sort()
{
  // binary insertion sort
  long b, c, e, i, k;

  for (i = 2; i <= count; i++)
  {
      b = 1;
      e = i-1;
      c = (b+e)/2;
      while (b != c)
      {
          if (_tcsicmp(items[c-1].szName, items[i-1].szName) > 0)
          {
            e = c;
          }
          else
          {
            b = c;
          }
          c = (b + e)/2;
      }
      if (_tcsicmp(items[b-1].szName, items[i-1].szName) < 0)
      {
          if (_tcsicmp(items[i-1].szName, items[e-1].szName) > 0)
          {
              b = e + 1;
          }
          else
          {
              b = e;
          }
      }
      k = i;
      SimContact tmp = items[i-1];
      while (k > b)
      {
          items[k-1] = items[k-1-1];
          k = k-1;
      }
      items[b-1] = tmp;
  }
}